package com.example.authserver.config;

import com.example.authserver.repository.JdbcUserDetailsRepository;
import com.example.authserver.model.Client;
import com.example.authserver.repository.JdbcClientRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.keys.KeyManager;
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * @author administrator
 */
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class SecurityConfig {


    /**
     * Give a client repository.
     *
     * @return RegisteredClientRepository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcClientRepository jdbcClientRepository) {

        BiFunction<Function<String, Optional<Client>>, String, RegisteredClient> findFunction =
                (method, param) -> method
                        .apply(param)
                        .map(Client::toRegisteredClient)
                        .orElse(null);
        return new RegisteredClientRepository() {
            @Override
            public RegisteredClient findById(String id) {
                return findFunction.apply(jdbcClientRepository::findById, id);
            }

            @Override
            public RegisteredClient findByClientId(String clientId) {
                return findFunction.apply(jdbcClientRepository::findByClientId, clientId);
            }
        };
    }


    /**
     * Give a user.
     *
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService userDetailsService(JdbcUserDetailsRepository jdbcUserDetailsRepository) {
        return jdbcUserDetailsRepository::findByUsername;
    }

    /**
     * Give a default key manager.
     *
     * @return keyManager
     */
    @Bean
    public KeyManager keyManager() {
        return new StaticKeyGeneratingKeyManager();
    }

    @Bean
    public OAuth2AuthorizationService oAuth2AuthorizationService(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, OAuth2Authorization> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.afterPropertiesSet();
        ValueOperations<String, OAuth2Authorization> opsForValue = template.opsForValue();

        return new OAuth2AuthorizationService() {
            private final String PREFIX = "oauth:code:";

            @Override
            public void save(OAuth2Authorization authorization) {
                Object key = authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE);
                opsForValue.set(PREFIX + key, authorization, 30, TimeUnit.MINUTES);
            }

            @Override
            public void remove(OAuth2Authorization authorization) {
                Object key = authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE);
                template.delete(PREFIX + key);
            }

            @Override
            public OAuth2Authorization findByToken(String s, TokenType tokenType) {
                return opsForValue.get(PREFIX + s);
            }
        };
    }
}
